/*
 *	Encodings.java
 *
 *	This file is part of Tritonus: http://www.tritonus.org/
 */

/*
 *  Copyright (c) 2000 by Florian Bomers <http://www.bomers.de>
 *
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library General Public License as published
 *   by the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

package org.tritonus.share.sampled;

import java.util.Iterator;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.AudioFormat;
import org.tritonus.share.StringHashedSet;

/**
 * This class is a proposal for generic handling of encodings. The main purpose
 * is to provide a standardized way of implementing encoding types. Like this,
 * encodings are only identified by their String name, and not, as currently, by
 * their object instance.
 * <p>
 * A registry of standard encoding names will be maintained by the Tritonus
 * team.
 * <p>
 * In a specification request to JavaSoft, the static method
 * <code>getEncoding</code> should be integrated into
 * <code>AudioFormat.Encoding(String name)</code> (possibly renamed to
 * <code>getInstance(String name)</code>.<br>
 * The static instances of ULAW, ALAW PCM_UNSIGNED and PCM_SIGNED encodings in
 * that class should be retrieved using that function, too (internally).<br>
 * At best, the protected constructor of that class should also be replaced to
 * be a private constructor. Like this it will be prevented that developers
 * create their own instances of Encoding, which causes problems with the equals
 * method. In fact, the equals method should be redefined anyway so that it
 * compares the names and not the objects.
 * <p>
 * Also, a specification request should be made to integrate
 * <code>getEncodings()</code> into AudioSystem (this is especially annoying as
 * the relevant methods already exist in the provider interfaces of file
 * readers, file writers and converters).
 * 
 * @author Florian Bomers
 */
public class Encodings extends AudioFormat.Encoding implements java.io.Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 7542364960891309820L;
	/** contains all known encodings */
	private static StringHashedSet<AudioFormat.Encoding> encodings = new StringHashedSet<AudioFormat.Encoding>();

	// initially add the standard encodings
	static {
		encodings.add(AudioFormat.Encoding.PCM_SIGNED);
		encodings.add(AudioFormat.Encoding.PCM_UNSIGNED);
		encodings.add(AudioFormat.Encoding.ULAW);
		encodings.add(AudioFormat.Encoding.ALAW);
	}

	Encodings(String name) {
		super(name);
	}

	/**
	 * Use this method for retrieving an instance of
	 * <code>AudioFormat.Encoding</code> of the specified name. A standard
	 * registry of encoding names will be maintained by the Tritonus team.
	 * <p>
	 * Every file reader, file writer, and format converter provider should
	 * exclusively use this method for retrieving instances of
	 * <code>AudioFormat.Encoding</code>.
	 */
	/*
	 * MP2000/09/11: perhaps it is not a good idea to allow user programs the
	 * creation of new encodings. The problem with it is that a plain typo will
	 * produce an encoding object that is not supported. Instead, some
	 * indication of an error should be signaled to the user program. And, there
	 * should be a second interface for service providers allowing them to
	 * register encodings supported by themselves.
	 * 
	 * $$fb2000/09/26: The problem is what you see as second issue: it can never
	 * be assured that this class knows all available encodings. So at the
	 * moment, there is no choice than to allow users to create any Encoding
	 * they wish. The encodings database will simplify things. A problem with an
	 * interface to retrieve supported encodings (or API function in
	 * spi.FormatConversionProvider) is that this requires loading of all
	 * providers very early. Hmmm, maybe this is necessary in any case when the
	 * user issues something like AudioSystem.isConversionSupported.
	 */
	public static AudioFormat.Encoding getEncoding(String name) {
		AudioFormat.Encoding res = encodings.get(name);
		if (res == null) {
			// it is not already in the string set. Create a new encoding
			// instance.
			res = new Encodings(name);
			// and save it for the future
			encodings.add(res);
		}
		return res;
	}

	/**
	 * Tests for equality of 2 encodings. They are equal when their strings
	 * match.
	 * <p>
	 * This function should be AudioFormat.Encoding.equals and must be
	 * considered as a temporary work around until it flows into the JavaSound
	 * API.
	 */
	// IDEA: create a special "NOT_SPECIFIED" encoding
	// and a AudioFormat.Encoding.matches method.
	public static boolean equals(AudioFormat.Encoding e1, AudioFormat.Encoding e2) {
		return e2.toString().equals(e1.toString());
	}

	/**
	 * Returns all &quot;supported&quot; encodings. Supported means that it is
	 * possible to read or write files with this encoding, or that a converter
	 * accepts this encoding as source or target format.
	 * <p>
	 * Currently, this method returns a best guess and the search algorithm is
	 * far from complete: with standard methods of AudioSystem, only the target
	 * encodings of the converters can be retrieved - neither the source
	 * encodings of converters nor the encodings of file readers and file
	 * writers cannot be retrieved.
	 */
	public static AudioFormat.Encoding[] getEncodings() {
		StringHashedSet<AudioFormat.Encoding> iteratedSources = new StringHashedSet<AudioFormat.Encoding>();
		StringHashedSet<AudioFormat.Encoding> retrievedTargets = new StringHashedSet<AudioFormat.Encoding>();
		Iterator<AudioFormat.Encoding> sourceFormats = encodings.iterator();
		while (sourceFormats.hasNext()) {
			AudioFormat.Encoding source = sourceFormats.next();
			iterateEncodings(source, iteratedSources, retrievedTargets);
		}
		return retrievedTargets.toArray(new AudioFormat.Encoding[retrievedTargets.size()]);
	}

	private static void iterateEncodings(AudioFormat.Encoding source, StringHashedSet<AudioFormat.Encoding> iteratedSources, StringHashedSet<AudioFormat.Encoding> retrievedTargets) {
		if (!iteratedSources.contains(source)) {
			iteratedSources.add(source);
			AudioFormat.Encoding[] targets = AudioSystem.getTargetEncodings(source);
			for (int i = 0; i < targets.length; i++) {
				AudioFormat.Encoding target = targets[i];
				if (retrievedTargets.add(target)) {
					iterateEncodings(target, iteratedSources, retrievedTargets);
				}
			}
		}
	}
}

/*** Encodings.java ***/

